import { defaultTestConfig } from '@prisma/config'
import { jestConsoleContext, jestContext } from '@prisma/get-platform'
import fs from 'fs'
import { join } from 'path'
import stripAnsi from 'strip-ansi'

import { defaultEnv, defaultSchema, Init } from '../Init'

const ctx = jestContext.new().add(jestConsoleContext()).assemble()

test('is schema and env written on disk replace', async () => {
  const recordedStdout = (await Init.new().parse([], defaultTestConfig())).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(defaultSchema())
  expect(schema).toMatchSnapshot()

  const env = fs.readFileSync(join(ctx.tmpDir, '.env'), 'utf-8')
  expect(env).toMatch(await defaultEnv(undefined))

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('prisma/schema.prisma')
  expect(config).toMatchInlineSnapshot(`
    "// This file was generated by Prisma and assumes you have installed the following:
    // npm install --save-dev prisma dotenv
    import "dotenv/config";
    import { defineConfig, env } from "prisma/config";

    export default defineConfig({
      schema: "prisma/schema.prisma",
      migrations: {
        path: "prisma/migrations",
      },
      datasource: {
        url: env("DATABASE_URL"),
      },
    });
    "
  `)
})

test('works with url param', async () => {
  ctx.fixture('init')
  const recordedStdout = (await Init.new().parse(['--url', 'file:dev.db'], defaultTestConfig())).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      datasourceProvider: 'sqlite',
    }),
  )
  expect(schema).toMatchSnapshot()

  const env = fs.readFileSync(join(ctx.tmpDir, '.env'), 'utf-8')
  expect(env).toMatchInlineSnapshot(`
    "# Environment variables declared in this file are NOT automatically loaded by Prisma.
    # Please add \`import "dotenv/config";\` to your \`prisma.config.ts\` file, or use the Prisma CLI with Bun
    # to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.

    # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
    # See the documentation for all the connection string options: https://pris.ly/d/connection-strings

    DATABASE_URL="file:dev.db""
  `)

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('url: env("DATABASE_URL")')
  expect(config).toMatchInlineSnapshot(`
    "// This file was generated by Prisma and assumes you have installed the following:
    // npm install --save-dev prisma dotenv
    import "dotenv/config";
    import { defineConfig, env } from "prisma/config";

    export default defineConfig({
      schema: "prisma/schema.prisma",
      migrations: {
        path: "prisma/migrations",
      },
      datasource: {
        url: env("DATABASE_URL"),
      },
    });
    "
  `)
})

test('works with provider param - postgresql', async () => {
  ctx.fixture('init')
  const recordedStdout = (
    await Init.new().parse(['--datasource-provider', 'postgresql'], defaultTestConfig())
  ).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      datasourceProvider: 'postgresql',
    }),
  )
  expect(schema).toMatchSnapshot()

  const env = fs.readFileSync(join(ctx.tmpDir, '.env'), 'utf-8')
  expect(env).toMatchInlineSnapshot(`
    "# Environment variables declared in this file are NOT automatically loaded by Prisma.
    # Please add \`import "dotenv/config";\` to your \`prisma.config.ts\` file, or use the Prisma CLI with Bun
    # to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.

    # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
    # See the documentation for all the connection string options: https://pris.ly/d/connection-strings

    DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public""
  `)

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('url: env("DATABASE_URL")')
  expect(config).toMatchInlineSnapshot(`
    "// This file was generated by Prisma and assumes you have installed the following:
    // npm install --save-dev prisma dotenv
    import "dotenv/config";
    import { defineConfig, env } from "prisma/config";

    export default defineConfig({
      schema: "prisma/schema.prisma",
      migrations: {
        path: "prisma/migrations",
      },
      datasource: {
        url: env("DATABASE_URL"),
      },
    });
    "
  `)
})

test('works with provider param - cockroachdb', async () => {
  ctx.fixture('init')
  const recordedStdout = (
    await Init.new().parse(['--datasource-provider', 'cockroachdb'], defaultTestConfig())
  ).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      datasourceProvider: 'cockroachdb',
    }),
  )
  expect(schema).toMatchSnapshot()

  const env = fs.readFileSync(join(ctx.tmpDir, '.env'), 'utf-8')
  expect(env).toMatchInlineSnapshot(`
    "# Environment variables declared in this file are NOT automatically loaded by Prisma.
    # Please add \`import "dotenv/config";\` to your \`prisma.config.ts\` file, or use the Prisma CLI with Bun
    # to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.

    # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
    # See the documentation for all the connection string options: https://pris.ly/d/connection-strings

    DATABASE_URL="postgresql://johndoe:randompassword@localhost:26257/mydb?schema=public""
  `)

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('works with provider and url params - cockroachdb', async () => {
  ctx.fixture('init')
  const recordedStdout = (
    await Init.new().parse(
      ['--datasource-provider', 'cockroachdb', '--url', 'postgres://prisma@localhost:26257/defaultdb'],
      defaultTestConfig(),
    )
  ).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      datasourceProvider: 'cockroachdb',
    }),
  )
  expect(schema).toMatchSnapshot()

  const env = fs.readFileSync(join(ctx.tmpDir, '.env'), 'utf-8')
  expect(env).toMatchInlineSnapshot(`
    "# Environment variables declared in this file are NOT automatically loaded by Prisma.
    # Please add \`import "dotenv/config";\` to your \`prisma.config.ts\` file, or use the Prisma CLI with Bun
    # to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.

    # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
    # See the documentation for all the connection string options: https://pris.ly/d/connection-strings

    DATABASE_URL="postgresql://johndoe:randompassword@localhost:26257/mydb?schema=public""
  `)

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('url: env("DATABASE_URL")')
  expect(config).toMatchSnapshot()
})

test('works with provider param - mysql', async () => {
  ctx.fixture('init')
  const recordedStdout = (await Init.new().parse(['--datasource-provider', 'mysql'], defaultTestConfig())).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      datasourceProvider: 'mysql',
    }),
  )
  expect(schema).toMatchSnapshot()

  const env = fs.readFileSync(join(ctx.tmpDir, '.env'), 'utf-8')
  expect(env).toMatchInlineSnapshot(`
    "# Environment variables declared in this file are NOT automatically loaded by Prisma.
    # Please add \`import "dotenv/config";\` to your \`prisma.config.ts\` file, or use the Prisma CLI with Bun
    # to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.

    # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
    # See the documentation for all the connection string options: https://pris.ly/d/connection-strings

    DATABASE_URL="mysql://johndoe:randompassword@localhost:3306/mydb""
  `)

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('works with provider param - SQLITE', async () => {
  ctx.fixture('init')
  const recordedStdout = (await Init.new().parse(['--datasource-provider', 'SQLITE'], defaultTestConfig())).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      datasourceProvider: 'sqlite',
    }),
  )
  expect(schema).toMatchSnapshot()

  const env = fs.readFileSync(join(ctx.tmpDir, '.env'), 'utf-8')
  expect(env).toMatchInlineSnapshot(`
    "# Environment variables declared in this file are NOT automatically loaded by Prisma.
    # Please add \`import "dotenv/config";\` to your \`prisma.config.ts\` file, or use the Prisma CLI with Bun
    # to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.

    # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
    # See the documentation for all the connection string options: https://pris.ly/d/connection-strings

    DATABASE_URL="file:./dev.db""
  `)

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('works with provider param - SqlServer', async () => {
  ctx.fixture('init')
  const recordedStdout = (
    await Init.new().parse(['--datasource-provider', 'SqlServer'], defaultTestConfig())
  ).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      datasourceProvider: 'sqlserver',
    }),
  )
  expect(schema).toMatchSnapshot()

  const env = fs.readFileSync(join(ctx.tmpDir, '.env'), 'utf-8')
  expect(env).toMatchInlineSnapshot(`
    "# Environment variables declared in this file are NOT automatically loaded by Prisma.
    # Please add \`import "dotenv/config";\` to your \`prisma.config.ts\` file, or use the Prisma CLI with Bun
    # to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.

    # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
    # See the documentation for all the connection string options: https://pris.ly/d/connection-strings

    DATABASE_URL="sqlserver://localhost:1433;database=mydb;user=SA;password=randompassword;""
  `)

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('works with provider param - MongoDB', async () => {
  ctx.fixture('init')
  const recordedStdout = (await Init.new().parse(['--datasource-provider', 'MongoDB'], defaultTestConfig())).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      datasourceProvider: 'mongodb',
    }),
  )
  expect(schema).toMatchSnapshot()

  const env = fs.readFileSync(join(ctx.tmpDir, '.env'), 'utf-8')
  expect(env).toMatchInlineSnapshot(`
    "# Environment variables declared in this file are NOT automatically loaded by Prisma.
    # Please add \`import "dotenv/config";\` to your \`prisma.config.ts\` file, or use the Prisma CLI with Bun
    # to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.

    # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
    # See the documentation for all the connection string options: https://pris.ly/d/connection-strings

    DATABASE_URL="mongodb+srv://root:randompassword@cluster0.ab1cd.mongodb.net/mydb?retryWrites=true&w=majority""
  `)

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('errors with invalid provider param', async () => {
  ctx.fixture('init')
  await expect(Init.new().parse(['--datasource-provider', 'INVALID'], defaultTestConfig())).rejects.toThrow()
})

test('works with --with-model param postgresql', async () => {
  ctx.fixture('init')
  const recordedStdout = (await Init.new().parse(['--with-model'], defaultTestConfig())).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(defaultSchema({ withModel: true, datasourceProvider: 'postgresql' }))
  expect(schema).toMatchSnapshot()

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('works with --with-model param mongodb', async () => {
  ctx.fixture('init')
  const recordedStdout = (
    await Init.new().parse(['--with-model', '--datasource-provider', 'MongoDB'], defaultTestConfig())
  ).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(defaultSchema({ withModel: true, datasourceProvider: 'mongodb' }))
  expect(schema).toMatchSnapshot()

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('works with --with-model param cockroachdb', async () => {
  ctx.fixture('init')
  const recordedStdout = (
    await Init.new().parse(['--with-model', '--datasource-provider', 'CockroachDB'], defaultTestConfig())
  ).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(defaultSchema({ withModel: true, datasourceProvider: 'cockroachdb' }))
  expect(schema).toMatchSnapshot()

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('works with generator param - `go run github.com/steebchen/prisma-client-go`', async () => {
  ctx.fixture('init')
  const recordedStdout = (
    await Init.new().parse(
      ['--generator-provider', 'go run github.com/steebchen/prisma-client-go'],
      defaultTestConfig(),
    )
  ).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      generatorProvider: 'go run github.com/steebchen/prisma-client-go',
    }),
  )
  expect(schema).toMatchSnapshot()

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('works with preview features - mock test', async () => {
  ctx.fixture('init')
  const recordedStdout = (await Init.new().parse(['--preview-feature', 'mock-123'], defaultTestConfig())).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      previewFeatures: ['mock-123'],
    }),
  )
  expect(schema).toMatchSnapshot()

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('works with preview features - multiple', async () => {
  ctx.fixture('init')
  const recordedStdout = (
    await Init.new().parse(['--preview-feature', 'mock-123', '--preview-feature', 'mock-456'], defaultTestConfig())
  ).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      previewFeatures: ['mock-123', 'mock-456'],
    }),
  )
  expect(schema).toMatchSnapshot()

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('works with custom output', async () => {
  ctx.fixture('init')
  const recordedStdout = (await Init.new().parse(['--output', './db'], defaultTestConfig())).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(
    defaultSchema({
      output: './db',
    }),
  )
  expect(schema).toMatchSnapshot()

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('warns when DATABASE_URL present in .env ', async () => {
  fs.writeFileSync(join(ctx.tmpDir, '.env'), `DATABASE_URL="postgres://dont:overwrite@me:5432/tests"`)
  const recordedStdout = (await Init.new().parse([], defaultTestConfig())).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(defaultSchema())
  expect(schema).toMatchSnapshot()

  const env = fs.readFileSync(join(ctx.tmpDir, '.env'), 'utf-8')
  expect(env).toMatch(`DATABASE_URL="postgres://dont:overwrite@me:5432/tests"`)

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('appends when .env present', async () => {
  fs.writeFileSync(join(ctx.tmpDir, '.env'), `SOMETHING="is here"`)
  const recordedStdout = (await Init.new().parse([], defaultTestConfig())).toString()
  expect(stripAnsi(recordedStdout)).toMatchSnapshot()

  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toMatch(defaultSchema())
  expect(schema).toMatchSnapshot()

  const env = fs.readFileSync(join(ctx.tmpDir, '.env'), 'utf-8')
  expect(env).toMatch(/SOMETHING="is here"/)
  expect(env).toMatch(/DATABASE_URL="prisma\+postgres:\/\/localhost:(\d+)\/\?api_key=eyJk.*TAifQ"/)

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('writes a minimal .gitignore file', async () => {
  ctx.fixture('init')
  await Init.new().parse([], defaultTestConfig())
  const gitignore = fs.readFileSync(join(ctx.tmpDir, '.gitignore'), 'utf-8')
  expect(gitignore).toMatchSnapshot()

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('do not replace .gitignore file if already present', async () => {
  ctx.fixture('init')
  const gitignorePath = join(ctx.tmpDir, '.gitignore')
  fs.writeFileSync(gitignorePath, `# This should not be overridden`)
  fs.readFileSync(gitignorePath, 'utf-8')
  await Init.new().parse([], defaultTestConfig())
  const gitignoreAfter = fs.readFileSync(gitignorePath, 'utf-8')
  expect(gitignoreAfter).toMatchSnapshot()

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})

test('uses determineClientOutputPath when no output is specified', async () => {
  ctx.fixture('client-output-path/with-lib')
  await Init.new().parse([], defaultTestConfig())
  const schema = fs.readFileSync(join(ctx.tmpDir, 'prisma', 'schema.prisma'), 'utf-8')
  expect(schema).toContain('output   = "../lib/generated/prisma"')

  const config = fs.readFileSync(join(ctx.tmpDir, 'prisma.config.ts'), 'utf-8')
  expect(config).toContain('defineConfig')
  expect(config).toMatchSnapshot()
})
